import { beforeEach, describe, expect, it } from 'esmocha';

import type { SinonStub } from 'sinon';
import sinon from 'sinon';

import { defaultHelpers as helpers, runResult } from '../../lib/testing/index.ts';
import { SERVER_MAIN_RES_DIR } from '../generator-constants.ts';

const GENERATOR_HEROKU = 'heroku';
const expectedFiles = {
  monolith: ['Procfile', `${SERVER_MAIN_RES_DIR}/config/bootstrap-heroku.yml`, `${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`],
};

const getSinonRunResultCalls = () => {
  return runResult.spawnStub!.getCalls().map((call: any) => call.args);
};

const createSpawnCommandReturn = (resolvedValue?: any, data?: any) =>
  Object.assign(
    Promise.resolve({
      exitCode: 0,
      stdout: '',
      stderr: '',
      ...resolvedValue,
    }),
    {
      ...data,
      stdout: { on: () => {} },
      stderr: { on: () => {} },
    },
  );

describe('generator - Heroku', () => {
  const herokuAppName = 'jhipster-test';
  let stub: SinonStub;

  beforeEach(() => {
    stub = sinon.stub();
    // Add catch all
    stub.withArgs('spawnCommand').returns(createSpawnCommandReturn());
    stub.withArgs('spawn').returns(createSpawnCommandReturn());

    stub.withArgs('spawnCommand', 'heroku plugins').returns(createSpawnCommandReturn({ stdout: 'heroku-cli-deploy', stderr: '' }));
  });
  afterEach(() => {
    stub.resetHistory();
  });

  describe('microservice application', () => {
    describe('with JAR deployment', () => {
      beforeEach(async () => {
        await helpers
          .runJHipster(GENERATOR_HEROKU)
          .withJHipsterConfig({ applicationType: 'microservice' })
          .withOptions({ skipBuild: true })
          .withAnswers({
            herokuAppName,
            herokuRegion: 'us',
            herokuDeployType: 'jar',
            herokuJHipsterRegistryApp: 'sushi',
            herokuJHipsterRegistryUsername: 'admin',
            herokuJHipsterRegistryPassword: 'changeme',
            herokuJavaVersion: '17',
          })
          .withSpawnMock(stub);
      });
      it('should match files snapshot', function () {
        expect(runResult.getSnapshot()).toMatchSnapshot();
      });
      it('creates expected files', () => {
        runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"');
      });
    });
  });

  describe('monolith application', () => {
    describe('with an unavailable app name', () => {
      const autogeneratedAppName = 'random-app-name';
      beforeEach(async () => {
        stub
          .withArgs('spawn', 'heroku', sinon.match(['create', herokuAppName]))
          .returns(createSpawnCommandReturn({ exitCode: 1, stderr: `Name ${herokuAppName} is already taken` }));
        stub
          .withArgs('spawn', 'heroku', sinon.match(['create']))
          .returns(createSpawnCommandReturn({ stdout: `https://git.heroku.com/${autogeneratedAppName}.git` }));

        await helpers
          .runJHipster(GENERATOR_HEROKU)
          .withJHipsterConfig()
          .withOptions({ skipBuild: true })
          .withAnswers({
            herokuAppName,
            herokuRegion: 'us',
            herokuDeployType: 'jar',
            herokuForceName: 'No',
            herokuJavaVersion: '11',
          })
          .withSpawnMock(stub);
      });
      it('should match files snapshot', function () {
        expect(runResult.getSnapshot()).toMatchSnapshot();
      });
      it('creates expected monolith files', () => {
        runResult.assertFile(expectedFiles.monolith);
        runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': { herokuAppName: autogeneratedAppName } });
      });
      it('calls should match snapshot', () => {
        expect(getSinonRunResultCalls()).toMatchSnapshot();
      });
    });

    describe('with Git deployment', () => {
      beforeEach(async () => {
        await helpers
          .runJHipster(GENERATOR_HEROKU)
          .withJHipsterConfig()
          .withAnswers({
            herokuAppName,
            herokuRegion: 'us',
            herokuDeployType: 'git',
            herokuJavaVersion: '11',
          })
          .withSpawnMock(stub);
      });
      it('should match files snapshot', function () {
        expect(runResult.getSnapshot()).toMatchSnapshot();
      });
      it('creates expected monolith files', () => {
        runResult.assertFile(expectedFiles.monolith);
        runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "git"');
      });
      it('calls should match snapshot', () => {
        expect(getSinonRunResultCalls()).toMatchSnapshot();
      });
    });

    describe('in the US', () => {
      beforeEach(async () => {
        await helpers
          .runJHipster(GENERATOR_HEROKU)
          .withJHipsterConfig()
          .withOptions({ skipBuild: true })
          .withAnswers({
            herokuAppName,
            herokuRegion: 'us',
            herokuDeployType: 'jar',
            herokuJavaVersion: '11',
          })
          .withSpawnMock(stub);
      });
      it('should match files snapshot', function () {
        expect(runResult.getSnapshot()).toMatchSnapshot();
      });
      it('creates expected monolith files', () => {
        runResult.assertFile(expectedFiles.monolith);
        runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"');
        runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:');
        runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:');
      });
      it('calls should match snapshot', () => {
        expect(getSinonRunResultCalls()).toMatchSnapshot();
      });
    });

    describe('in the EU', () => {
      beforeEach(async () => {
        await helpers
          .runJHipster(GENERATOR_HEROKU)
          .withJHipsterConfig()
          .withOptions({ skipBuild: true })
          .withAnswers({
            herokuAppName,
            herokuRegion: 'eu',
            herokuDeployType: 'jar',
            herokuJavaVersion: '11',
          })
          .withSpawnMock(stub);
      });
      it('should match files snapshot', function () {
        expect(runResult.getSnapshot()).toMatchSnapshot();
      });
      it('creates expected monolith files', () => {
        runResult.assertFile(expectedFiles.monolith);
      });
      it('calls should match snapshot', () => {
        expect(getSinonRunResultCalls()).toMatchSnapshot();
      });
    });

    describe('with PostgreSQL', () => {
      beforeEach(async () => {
        await helpers
          .runJHipster(GENERATOR_HEROKU)
          .withJHipsterConfig()
          .withOptions({ skipBuild: true })
          .withAnswers({
            herokuAppName,
            herokuRegion: 'eu',
            herokuDeployType: 'jar',
            herokuJavaVersion: '11',
          })
          .withSpawnMock(stub);
      });
      it('should match files snapshot', function () {
        expect(runResult.getSnapshot()).toMatchSnapshot();
      });
      it('creates expected monolith files', () => {
        runResult.assertFile(expectedFiles.monolith);
        runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:');
        runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:');
      });
      it('calls should match snapshot', () => {
        expect(getSinonRunResultCalls()).toMatchSnapshot();
      });
    });

    describe('with existing app', () => {
      const existingHerokuAppName = 'jhipster-existing';
      beforeEach(async () => {
        stub
          .withArgs('spawn', 'heroku', sinon.match(['apps:info', '--json', existingHerokuAppName]))
          .returns(createSpawnCommandReturn({ stdout: `{"app":{"name":"${existingHerokuAppName}"}, "dynos":[]}` }));
        await helpers
          .runJHipster(GENERATOR_HEROKU)
          .withJHipsterConfig<any>({ herokuAppName: 'jhipster-existing', herokuDeployType: 'git' })
          .withOptions({ skipBuild: true })
          .withSpawnMock(stub);
      });
      it('should match files snapshot', function () {
        expect(runResult.getSnapshot()).toMatchSnapshot();
      });
      it('creates expected monolith files', () => {
        runResult.assertFile(expectedFiles.monolith);
        runResult.assertFileContent('.yo-rc.json', `"herokuAppName": "${existingHerokuAppName}"`);
      });
      it('calls should match snapshot', () => {
        expect(getSinonRunResultCalls()).toMatchSnapshot();
      });
    });

    describe('with elasticsearch', () => {
      beforeEach(async () => {
        await helpers
          .runJHipster(GENERATOR_HEROKU)
          .withJHipsterConfig({ searchEngine: 'elasticsearch' })
          .withOptions({ skipBuild: true })
          .withAnswers({
            herokuAppName,
            herokuRegion: 'us',
            herokuDeployType: 'jar',
            herokuJavaVersion: '11',
          })
          .withSpawnMock(stub);
      });
      it('should match files snapshot', function () {
        expect(runResult.getSnapshot()).toMatchSnapshot();
      });
      it('creates expected monolith files', () => {
        runResult.assertFile(expectedFiles.monolith);
        runResult.assertFileContent('.yo-rc.json', '"herokuDeployType": "jar"');
        runResult.assertFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'datasource:');
        runResult.assertNoFileContent(`${SERVER_MAIN_RES_DIR}/config/application-heroku.yml`, 'mongodb:');
      });
      it('calls should match snapshot', () => {
        expect(getSinonRunResultCalls()).toMatchSnapshot();
      });
    });
  });
});
